/* A generic, flexible, static factory written in C# contained in a single file for 
 * easy inclusion in any project. You'll never have to write another factory again. 
 * The main points of extensibility are exposed so you can control it's behavior 
 * without writing boilerplate code. Visit the project for samples that show you 
 * how you can easily factory file extension specific classes, version classes or 
 * protocols and much more. Part of Code Blocks (http://codeblocks.codeplex.com)
 */

using System;
using System.Collections.Generic;

/* Generic, flexible static factory written by Ananth Balasubramaniam http://www.ananthonline.net
   Project page: http://genericfactory.codeplex.com */
static class Factory<TKey, TValue>
{
	private static readonly SortedDictionary<TKey, Type> RegisteredTypes = new SortedDictionary<TKey, Type>();

	public static void Discover(IEnumerable<Type> types, Func<Type, IEnumerable<TKey>> keyGenerator)
	{
		var factoryType = typeof(TValue);
		foreach (var type in types)
			if (factoryType.IsAssignableFrom(type))
			{
				var keys = keyGenerator(type);
				foreach (var key in keys)
						RegisteredTypes.Add(key, type);
			}
	}

	public static void Discover(Func<Type, IEnumerable<TKey>> keyGenerator)
	{
		foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
			try
			{
				Discover(assembly.GetTypes(), keyGenerator);
			}
			catch
			{
				continue;
			}
	}

	public static void Register(TKey key, Type type)
	{
		if (typeof(TValue).IsAssignableFrom(type))
			RegisteredTypes.Add(key, type);
		else
			throw new InvalidOperationException(string.Format("Cannot cast/convert {0} to {1} using a boxing or user defined conversion", type.FullName, typeof(TValue).FullName));
	}

	public static void Register<T>(TKey key) where T: TValue
	{
		RegisteredTypes.Add(key, typeof(T));
	}

	public static bool Contains(TKey key)
	{
		return RegisteredTypes.ContainsKey(key);
	}

	public static void Unregister(TKey key)
	{
		if (RegisteredTypes.ContainsKey(key))
			RegisteredTypes.Remove(key);
	}

	public static TValue CreateForExactMatch(TKey key, IComparer<TKey> comparer, TValue defaultValue = default(TValue), params object[] args)
	{
		if (comparer == null)
			throw new ArgumentNullException("comparer");

		foreach (var kvp in RegisteredTypes)
			if (comparer.Compare(kvp.Key, key) == 0)
				return (TValue)Activator.CreateInstance(kvp.Value, args);

		return defaultValue;
	}

	public static TValue CreateForExactMatch(TKey key, TValue defaultValue = default(TValue), params object[] args)
	{
		foreach (var kvp in RegisteredTypes)
		{
			var comparableKey = kvp.Key as IComparable<TKey>;
			if (comparableKey == null)
				throw new InvalidCastException(string.Format("Cannot cast {0} to IComparable<{0}>. Use the overload and pass in a Comparer<{0}> instead", typeof(TKey)));
			if (comparableKey.CompareTo(key) == 0)
				return (TValue) Activator.CreateInstance(kvp.Value, args);
		}

		return defaultValue;
	}

	public static TValue CreateForLastKeyLessThan(TKey key, IComparer<TKey> comparer, TValue defaultValue = default(TValue), params object[] args)
	{
		if (comparer == null)
			throw new ArgumentNullException("comparer");

		Type last = null;
		foreach (var kvp in RegisteredTypes)
		{
			if (comparer.Compare(kvp.Key, key) >= 0)
				return last == null ? default(TValue) : (TValue)Activator.CreateInstance(last, args);

			last = kvp.Value;
		}

		return last == null ? defaultValue : (TValue)Activator.CreateInstance(last, args);
	}
	public static TValue CreateForLastKeyLessThan(TKey key, TValue defaultValue = default(TValue), params object[] args)
	{
		return CreateForLastKeyLessThanOrEqualTo(key, Comparer<TKey>.Default, defaultValue, args);
	}

	public static TValue CreateForLastKeyLessThanOrEqualTo(TKey key, IComparer<TKey> comparer, TValue defaultValue = default(TValue), params object[] args)
	{
		if (comparer == null)
			throw new ArgumentNullException("comparer");

		Type last = null;
		foreach (var kvp in RegisteredTypes)
		{
			if (comparer.Compare(kvp.Key, key) == 0)
				return (TValue)Activator.CreateInstance(kvp.Value, args);
			if (comparer.Compare(kvp.Key, key) >= 0)
				return last == null ? default(TValue) : (TValue)Activator.CreateInstance(last, args);

			last = kvp.Value;
		}

		return last == null ? defaultValue : (TValue)Activator.CreateInstance(last, args);
	}
	public static TValue CreateForLastKeyLessThanOrEqualTo(TKey key, TValue defaultValue = default(TValue), params object[] args)
	{
		return CreateForLastKeyLessThanOrEqualTo(key, Comparer<TKey>.Default, defaultValue, args);
	}

	public static TValue CreateForFirstKeyGreaterThan(TKey key, IComparer<TKey> comparer, TValue defaultValue = default(TValue), params object[] args)
	{
		if (comparer == null)
			throw new ArgumentNullException("comparer");

		foreach (var kvp in RegisteredTypes)
			if (comparer.Compare(kvp.Key, key) > 0)
				return (TValue)Activator.CreateInstance(kvp.Value, args);

		return defaultValue;
	}
	public static TValue CreateForFirstKeyGreaterThan(TKey key, TValue defaultValue = default(TValue), params object[] args)
	{
		return CreateForFirstKeyGreaterThan(key, Comparer<TKey>.Default, defaultValue, args);
	}

	public static TValue CreateForFirstKeyGreaterThanOrEqualTo(TKey key, IComparer<TKey> comparer, TValue defaultValue = default(TValue), params object[] args)
	{
		if (comparer == null)
			throw new ArgumentNullException("comparer");

		foreach (var kvp in RegisteredTypes)
		{
			if (comparer.Compare(kvp.Key, key) == 0)
				return (TValue)Activator.CreateInstance(kvp.Value, args);
			if (comparer.Compare(kvp.Key, key) > 0)
				return (TValue) Activator.CreateInstance(kvp.Value, args);
		}

		return defaultValue;
	}
	public static TValue CreateForFirstKeyGreaterThanOrEqualTo(TKey key, TValue defaultValue = default(TValue), params object[] args)
	{
		return CreateForFirstKeyGreaterThanOrEqualTo(key, Comparer<TKey>.Default, defaultValue, args);
	}
}